﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Discovery;
using SsoaFramework.Core.Other;

namespace SsoaFramework.Core
{
    public class ServiceRegistry : IEnumerable<Service>
    {
        private readonly EnterpriseServiceBus esb;
        private readonly Uri servicesAddress;
        private readonly Uri metadataAddress;
        private readonly List<Service> services = new List<Service>();
        private readonly DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
        private readonly ServiceHost announcementServiceHost;

        public Service this[int i] { get { return services[i]; } }
        public List<Service> Services { get { return services; } }
        public event EventHandler<NewServiceFoundEventArgs> NewServiceFound;

        public ServiceRegistry(Uri servicesAddress, Uri metadataAddress, EnterpriseServiceBus esb)
        {
            this.servicesAddress = servicesAddress;
            this.metadataAddress = metadataAddress;
            this.esb = esb;
            discoveryClient.FindProgressChanged += new EventHandler<FindProgressChangedEventArgs>(OnServiceFound);
            AnnouncementService announcementService = new AnnouncementService();
            announcementService.OnlineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(OnOnlineEvent);
            announcementService.OfflineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(OnOfflineEvent);
            announcementServiceHost = new ServiceHost(announcementService);
            announcementServiceHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());
        }

        void OnOnlineEvent(object sender, AnnouncementEventArgs e)
        {
            Log.Instance.Info("New service went online");
            bool isMetadata = e.EndpointDiscoveryMetadata.ContractTypeNames[0].Name == "IMetadataExchange";
            if (!isMetadata)
            {
                bool isDuplicate = services.Any(s => s.Address == e.EndpointDiscoveryMetadata.Address);
                if (!isDuplicate)
                {
                    Service service = new Service(e.EndpointDiscoveryMetadata, servicesAddress, metadataAddress);
                    services.Add(service);
                    if(esb.ExposeService.ClientEndpoint!=null)
                        esb.ExposeService.ClientEndpoint.ServiceAppeared(service);
                    InvokeNewServiceFound(new NewServiceFoundEventArgs(service));
                }
            }
        }

        void OnOfflineEvent(object sender, AnnouncementEventArgs e)
        {
            Log.Instance.Info("New service went offline");
            var servicesWithOfflineEndpoint = services.Where(s => s.Address == e.EndpointDiscoveryMetadata.Address);
            if (servicesWithOfflineEndpoint.Count() == 0)
                Log.Instance.Warn("OnOfflineEvent. No service found in repository to go offline");
            else if (servicesWithOfflineEndpoint.Count() == 1)
            {
                var service = servicesWithOfflineEndpoint.First();
                if (esb.ExposeService.ClientEndpoint != null)
                    esb.ExposeService.ClientEndpoint.ServiceDisappeared(servicesWithOfflineEndpoint.First());
                services.Remove(service);
            }
            else
            {
                Log.Instance.Error("OnOffline service found. Multiple service with the same endpoint.");
                throw new Exception("OnOffline service found. Multiple service with the same endpoint.");
            }
        }

        public void Start()
        {
            FindServices();
            StartSearching();
        }

        public void StartSearching()
        {
            Log.Instance.Info("Searching for services");
            announcementServiceHost.Open();
        }

        public void StopSearching()
        {
            Log.Instance.Info("Stopping searching for services");
            announcementServiceHost.Close();
        }

        public void FindServices()
        {
            Log.Instance.Debug("Finding services");
            discoveryClient.FindAsync(new FindCriteria(), this);
        }

        void OnServiceFound(object sender, FindProgressChangedEventArgs e)
        {
            Log.Instance.Trace("Found some service!");
            bool isMetadata = e.EndpointDiscoveryMetadata.ContractTypeNames[0].Name == "IMetadataExchange";
            if (!isMetadata)
            {
                bool isDuplicate = services.Any(s => s.Address == e.EndpointDiscoveryMetadata.Address);
                if (!isDuplicate)
                {
                    Service service = new Service(e.EndpointDiscoveryMetadata, servicesAddress, metadataAddress);
                    services.Add(service);
                    InvokeNewServiceFound(new NewServiceFoundEventArgs(service));
                }
            }
        }

        void OnFindCompleted(object sender, FindCompletedEventArgs e)
        {
            Log.Instance.Debug("Finding services completed");
        }

        void InvokeNewServiceFound(NewServiceFoundEventArgs e)
        {
            EventHandler<NewServiceFoundEventArgs> handler = NewServiceFound;
            if (handler != null) handler(this, e);
        }

        public IEnumerator<Service> GetEnumerator()
        {
            return services.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return services.GetEnumerator();
        }
    }
}
